﻿package com.janisRadins.asDraw {
	import com.janisRadins.asDraw.vo.CurveData;
	import flash.display.GraphicsPath;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.geom.Point;
	/**
	 * ...
	 * @author Jānis Radiņš
	 */
	public class PathCollection extends EventDispatcher	implements ICurve, IPathCollection {
		/**
		 * Out of range error ID
		 */
		public const ERROR_OOR:uint = 1;
		/**
		 * Curve not found error ID
		 */
		public const ERROR_CNF:uint = 2;
		
		/**
		 * dataChanged event constant
		 * @eventType dataChanged
		 */
		public const DATA_CHANGED:String = "dataChanged";
		/**
		 * collectionChanged event constant
		 * @eventType collectionChanged
		 */
		public const COLLECTION_CHANGED:String = "collectionChanged";
		
		protected var pathList:Vector.<Path> = null;
		protected var _pathIndex:uint = 0;
		protected var _x:Number = 0;
		protected var _y:Number = 0;
		protected var _length:Number = NaN;
		
		public function PathCollection(paths:Vector.<Path> = null) {
			super();
			if (paths == null) {
				pathList = new Vector.<Path>();
			} else {
				pathList = paths;
			}
		}
		
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#pathCount
		 */
		public function get pathCount():uint {
			return pathList.length;
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#pathIndex
		 */
		public function get pathIndex():uint {
			return _pathIndex;
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#getPathAt()
		 */
		public function getPathAt(index:int):Path {
			if (index < 0) {
				index += pathList.length;
			}
			if (index >= pathList.length) {
				throw new Error("Path index [" + index + "] out of range", ERROR_OOR);
			}
			return pathList[index];
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#push()
		 */
		public function push(...paths):uint {
			pathList.push.apply(null, paths);
			_length = NaN;
			dispatchEvent(new Event(COLLECTION_CHANGED));
			return pathList.length;
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#setPathAt()
		 */
		public function setPathAt(path:Path, index:int):void {
			if (index < 0) {
				index += pathList.length;
			}
			if (index >= pathList.length) {
				throw new Error("Path index [" + index + "] out of range", ERROR_OOR);
			}
			pathList.splice(index, 0, [path]);
			_length = NaN;
			dispatchEvent(new Event(COLLECTION_CHANGED));
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#changePathIndex()
		 */
		public function changePathIndex(oldIndex:int, newIndex:int):void {
			if (oldIndex < 0)	oldIndex += pathList.length;
			if (newIndex < 0)	newIndex += pathList.length;
			
			if (oldIndex >= pathList.length || newIndex >= pathList.length) {
				throw new Error("Some index out of range", ERROR_OOR);
			}
			pathList.splice(newIndex, 0, pathList.splice(oldIndex, 1));
			dispatchEvent(new Event(COLLECTION_CHANGED));
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#removePath()
		 */
		public function removePath(path:Path):Path {
			if (contains(path) == false) {
				throw new Error("Curve not found", ERROR_CNF)
			}
			while (true) {
				var index:int = pathList.indexOf(path);
				if (index == -1) {
					break;
				}
				pathList.splice(index, 1);
			}
			if (contains(path) == false) {
				// TODO: Jāsaskaņo eventi ar Path klasi
				//path.removeEventListener(CurveChangeEvent.CHANGE, onCurveValueChange);
			}
			_length = NaN;
			dispatchEvent(new Event(COLLECTION_CHANGED));
			return path;
		}
		
/* Implementation of IPathCollection */
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#removePathAt()
		 */
		public function removePathAt(index:int):Path {
			if (index < 0) {
				index += pathList.length;
			}
			if (index >= pathList.length) {
				throw new Error("Path index [" + index + "] out of range", ERROR_OOR);
			}
			var path:Path = pathList.splice(index, 1)[0];
			if (contains(path) == false) {
				//path.removeEventListener(CurveChangeEvent.CHANGE, onCurveValueChange);
			}
			_length = NaN;
			dispatchEvent(new Event(COLLECTION_CHANGED));
			return path;
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#contains()
		 */
		public function contains(path:Path):Boolean {
			return (pathList.indexOf(path) != -1);
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#getPathData()
		 */
		public function getPathData(path:Path):CurveData {
			var startPoint:Point = new Point(_x, _y);
			var startLength:Number = 0;
			var subPath:Path;
			var lastCurveData:CurveData;
			for (var i:uint = 0; i < pathList.length; i++) {
				subPath = pathList[i];
				if (path == subPath) {
					return new CurveData(startPoint, startLength, subPath, i);
				}
				startPoint = startPoint.add(subPath.pathClosePoint);
				startLength += subPath.length;
			}
			return null;
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#hasNext
		 */
		public function get hasNext():Boolean {
			return _pathIndex < pathList.length - 1;
			
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#getNext()
		 */
		public function getNext():Path {
			if (hasNext == false) {
				return null;
			}
			return pathList[_pathIndex++];
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#hasPrev
		 */
		public function get hasPrev():Boolean {
			return _pathIndex > 0;
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#getPrev()
		 */
		public function getPrev():Path {
			if (hasPrev == false) {
				return null;
			}
			return pathList[_pathIndex--];
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#resetToStart()
		 */
		public function resetToStart():void {
			_pathIndex = 0;
		}
		/**
		 * @copy com.janisRadins.asDraw.IPathCollection#resetToEnd()
		 */
		public function resetToEnd():void {
			_pathIndex = pathList.length-1;
		}
		
/* Implementation of ICurve */
		/**
		 * @copy com.janisRadins.asDraw.ICurve#getPointOnCurve()
		 */
		public function getPointOnCurve(position:Number):Point {
			var pathPos:CurveData = getPathOnPosition(position);
			if (pathPos == null) {
				throw new Error("Path for requested position [" + position + "] not found");
			}
			return pathPos.coordinates.add(pathPos.curve.getPointOnCurve(position - pathPos.startLength));
		}
		/**
		 * @copy com.janisRadins.asDraw.ICurve#getAngleAtPoint()
		 */
		public function getAngleAtPoint(position:Number):Number {
			var pathPos:CurveData = getPathOnPosition(position);
			return pathPos.curve.getAngleAtPoint(position - pathPos.startLength);
		}
		/**
		 * @copy com.janisRadins.asDraw.ICurve#getCurveSegment()
		 */
		public function getCurveSegment(start:Number = NaN, end:Number = NaN):GraphicsPath {
			// todo: here
			if (isNaN(start))	start = 0;
			if (isNaN(end))		end = length;
			if (start == end)	return new GraphicsPath();
			if (start < end)	return getSubSegment(start, end);
			else				return DrawUtils.joinGraphicPaths(getSubSegment(start, length), getSubSegment(0, end));
			return null;
		}
		/**
		 * @copy com.janisRadins.asDraw.ICurve#length
		 */
		public function get length():Number {
			if (isNaN(_length) == true) {
				updateLength();
			}
			return _length;
		}
		
		public function get x():Number { return _x; }
		public function set x(value:Number):void {
			_x = value;
		}
		
		public function get y():Number { return _y; }
		public function set y(value:Number):void {
			_y = value;
		}
		
		private function getPathOnPosition(position:Number):CurveData {
			var startPoint:Point = new Point(_x, _y);
			var startLength:Number = 0;
			var subPath:Path;
			for (var i:uint = 0; i < pathList.length; i++) {
				subPath = pathList[i];
				if (position >= startLength && position <= startLength + subPath.length) {
					return new CurveData(startPoint, startLength, subPath, i);
				}
				startLength += subPath.length;
			}
			return null;
		}
		
		private function updateLength():void {
			_length = 0;
			for each(var path:Path in pathList) {
				_length += path.length;
			}
		}
		
		protected function getSubSegment(start:Number, end:Number):GraphicsPath {
			var startCurve:CurveData = getPathOnPosition(start);
			var startPoint:Point = new Point(startCurve.coordinates.x, startCurve.coordinates.y);
			var pathData:GraphicsPath = new GraphicsPath();
			pathData.moveTo(startPoint.x, startPoint.y);
			var segLength:Number = end - start;
			var path:Path;
			var s:Number = Math.max(start - startCurve.startLength, 0);
			var e:Number;
			var subPath:GraphicsPath;
			var lastCurveData:CurveData;
			for (var i:uint = startCurve.index; i < pathList.length; i++) {
				path = pathList[i] as Path;
				e = Math.min(segLength + s, path.length);
				subPath = path.getCurveSegment(s, e);
				DrawUtils.moveGraphicPath(subPath, startPoint);
				pathData = DrawUtils.joinGraphicPaths(pathData, subPath);
				segLength -= e-s;
				if (segLength == 0)	break;
				if (s > 0)			s = 0;
			}
			return pathData;
		}
	}
}